/********************************************************************
 * File: gcdump.c
 *
 * dump gc bin to dot file
 *
 * Author: FrankLi <lgl88911@163.com>
 *
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>

#define GC_FUN_MAX		100

#define GC_FIND_NONE	0
#define GC_FIND_DONE	1

#define GC_ATTR_NORMAL		0
#define GC_ATTR_LEAF		1

typedef struct _st_gtrace_node
{
	void *father;
	void *me;
	int cnt;
}st_gtrace_node;

typedef struct
{
	void *address;
	int attr;
	char name[64];
}st_fun_map;

char *add2line;
char *image;
st_fun_map gcfun[GC_FUN_MAX];
int funcnt;

void gcgetfunction( unsigned int address, char *func )
{
	FILE *fp;
	char cmd[100];
	int  len, i;

	sprintf(cmd, "%s -e %s -f -s 0x%x", add2line, image, address );

	fp = popen( cmd, "r" );

	if (fp == NULL)
	{
		return;
	}
	else
	{

		len = fread( cmd, 99, 1, fp);
		i = 0;
		while ( i < strlen(cmd) ) 
		{
			if ((cmd[i] == 0x0d) || (cmd[i] == 0x0a)) 
			{
				func[i] = 0;
				break;
			}
			else
			{
				func[i] = cmd[i];
			}
			i++;
		}

		pclose(fp);
  	}

	if(strcmp(func, "??") == 0)
	{
		sprintf(func, "unknow%x", address);
	}
}


st_gtrace_node *gcallocmem(char *path, int *fd, int *len)
{
	unsigned long filesize = -1;      
	struct stat statbuff;  
	
	if(stat(path, &statbuff) < 0)
	{  
		return NULL;  
	}
	else
	{  
		filesize = statbuff.st_size;  
	}

	filesize /= sizeof(st_gtrace_node);
	filesize *= sizeof(st_gtrace_node);
	if(filesize == 0)
	{
		return NULL;
	}

	*fd = open(path, O_RDONLY);
	if(*fd < 0)
	{
		return NULL;
	}

	*len = filesize;

	return mmap(NULL, filesize, PROT_READ, MAP_SHARED, *fd, 0);
}

void gcfreemem(void *gcmem, int fd, int len)
{
	munmap(gcmem, len);
	close(fd);
}

void gcdump(st_gtrace_node *gcmem, int len, FILE *dot)
{
	st_gtrace_node *node = gcmem;
	int i,j;
	int free;
	int find;
	//Get address-function map
	memset(gcfun, 0, sizeof(gcfun));
	funcnt = 0;	
	for(i=0; i<len/sizeof(st_gtrace_node); i++)
	{
		if(node[i].me != NULL)
		{
			find = GC_FIND_NONE;
			for(j=0;j<funcnt;j++)
			{
				if(gcfun[j].address == node[i].me)
				{
					find = GC_FIND_DONE;
					break;
				}
			}

			if(find == GC_FIND_NONE)
			{
				gcfun[funcnt].address = node[i].me;
				gcgetfunction(gcfun[funcnt].address, gcfun[funcnt].name);
				gcfun[funcnt].attr = GC_ATTR_LEAF;
				funcnt++;
			}

			find = GC_FIND_NONE;
			for(j=0;j<funcnt;j++)
			{
				if(gcfun[j].address == node[i].father)
				{
					find = GC_FIND_DONE;
					break;
				}
			}

			if(find == GC_FIND_NONE)
			{
				gcfun[funcnt].address = node[i].father;
				gcgetfunction(gcfun[funcnt].address, gcfun[funcnt].name);
				gcfun[funcnt].attr = GC_ATTR_LEAF;
				funcnt++;
			}
		}
	}

	//dram head
	 fprintf(dot, "digraph %s {\n\n", image);
	//find leaf & dram node
	for(i=0; i<funcnt; i++)
	{
		for(j=0; j<len/sizeof(st_gtrace_node); j++)
		{
			if(gcfun[i].address == node[j].father)
			{
				gcfun[i].attr = GC_ATTR_NORMAL;
				break;
			}
		}

		if(gcfun[i].attr == GC_ATTR_LEAF)
		{
			fprintf( dot, "  %s [shape=rectangle]\n", gcfun[i].name);
		}
		else
		{
			fprintf( dot, "  %s [shape=ellipse]\n", gcfun[i].name);
		}
	}


	//draw line
	for(i=0; i<len/sizeof(st_gtrace_node); i++)
	{
		char *from = NULL;
		char *to = NULL;

		for(j=0;j<funcnt;j++)
		{
			if(node[i].father == gcfun[j].address)
			{
				from = gcfun[j].name;
			}

			if(node[i].me == gcfun[j].address)
			{
				to = gcfun[j].name;
			}

			if(from != NULL && to != NULL)
			{
				break;
			}
		}
		
		fprintf( dot, "  %s -> %s [label=\"%d calls\" fontsize=\"10\"]\n", 
                  from, to, node[i].cnt );
	}

	//draw end
	fprintf( dot, "\n}\n" );

}

int main( int argc, char *argv[] )
{
	FILE *dot;
	DIR *dir;
	struct dirent *dirent;
	st_gtrace_node *gcmem;
	int len;
	int fd;
	char filename[256];
	char type;
	unsigned int address;

	if (argc != 4) {
		printf("Usage: \n");
		printf("	gcdump <add2line> <image> <file>\n");
		printf("	gcdump <add2line> <image> <folder>\n");
		exit(-1);
	}


	add2line = argv[1];
	image = argv[2];
	
	dir = opendir(argv[3]);
	if(dir == NULL)
	{
		printf("%s %d\n", __FILE__, __LINE__);
		gcmem = gcallocmem(argv[3], &fd, &len);
		if(fd == NULL)
		{
			printf("Can't open file or folder <%s>\n", argv[3]);
			exit(-1);
		}
		
		snprintf(filename, 256,"%s.dot", argv[3]);
		dot = fopen(filename, "w");
		if(dot == NULL)
		{
			printf("Can't open file  <%s>\n", filename);
			exit(-1);
		}

		gcdump(gcmem, len, dot);

		fclose(dot);
		
		gcfreemem(gcmem, fd, len);
	}
	else
	{
		while((dirent = readdir(dir))  != NULL )
		{
			if(dirent->d_type == DT_REG)
			{
				if(strstr(dirent->d_name, ".gc") != NULL &&
					strstr(dirent->d_name, ".dot") == NULL)
				{
					gcmem = gcallocmem(dirent->d_name, &fd, &len);
					if(fd == NULL)
					{
						printf("Can't open file or folder <%s>\n", dirent->d_name);
						exit(-1);
					}
					
					snprintf(filename, 256,"%s.dot", dirent->d_name);
					dot = fopen(filename, "w");
					if(dot == NULL)
					{
						printf("Can't open file  <%s>\n", filename);
						exit(-1);
					}

					gcdump(gcmem, len, dot);

					fclose(dot);
					
					gcfreemem(gcmem, fd, len);
				}
			}
		}

		closedir(dir);
	}
	
	return 0;
}


